/*
 * Copyright (c) 2025 Huawei Device Co., Ltd.
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */


import { ArkMethod, ArkFile, ArkAssignStmt, Stmt } from 'arkanalyzer/lib';
import Logger, { LOG_MODULE_TYPE } from 'arkanalyzer/lib/utils/logger';
import { BaseChecker, BaseMetaData } from '../BaseChecker';
import { Defects, IssueReport } from '../../model/Defects';
import { ClassMatcher, FileMatcher, MatcherCallback, MatcherTypes, MethodMatcher } from '../../matcher/Matchers';
import { Rule } from '../../model/Rule';
import { RuleListUtil } from '../../utils/common/DefectsList';
const defaultOptions: Options = {
    allowAny: false,
    allowBoolean: false,
    allowNever: false,
    allowNullish: false,
    allowNumber: false,
    allowRegExp: false,
};
const handleRigregex = /\w+\s*\|\|\s*'[^']+'/;
interface Options {
    /** Whether to allow `any` typed values in template expressions. */
    allowAny?: boolean;
    /** Whether to allow `boolean` typed values in template expressions. */
    allowBoolean?: boolean;
    /** Whether to allow `never` typed values in template expressions. */
    allowNever?: boolean;
    /** Whether to allow `nullish` typed values in template expressions. */
    allowNullish?: boolean;
    /** Whether to allow `number` typed values in template expressions. */
    allowNumber?: boolean;
    /** Whether to allow `regexp` typed values in template expressions. */
    allowRegExp?: boolean;
};
const logger = Logger.getLogger(LOG_MODULE_TYPE.HOMECHECK, 'RestrictTemplateExpressionsCheck');
const gmetaData: BaseMetaData = {
    severity: 2,
    ruleDocPath: 'docs/restrict-template-expressions-check.md',
    description: 'Enforce template literal expressions to be of string type.'
};
export class RestrictTemplateExpressionsCheck implements BaseChecker {
    readonly metaData: BaseMetaData = gmetaData;

    public rule: Rule;
    public defects: Defects[] = [];
    public issues: IssueReport[] = [];
    private fileMatcher: FileMatcher = {
        matcherType: MatcherTypes.FILE,
    };
    private classMatcher: ClassMatcher = {
        file: [this.fileMatcher],
        matcherType: MatcherTypes.CLASS
    };
    private methodMatcher: MethodMatcher = {
        matcherType: MatcherTypes.METHOD,
        class: [this.classMatcher]
    };
    public registerMatchers(): MatcherCallback[] {
        const matchBuildCb: MatcherCallback = {
            matcher: this.methodMatcher,
            callback: this.check
        };
        return [matchBuildCb];
    }
    public check = (method: ArkMethod): void => {
        // 将规则选项转换为Options类型
        let options: Options;
        if (this.rule && this.rule.option.length > 0) {
            options = this.rule.option[0] as Options;
        } else {
            options = defaultOptions;
        }
        let targetVariableNames: string[] = [];
        if (method instanceof ArkMethod) {
            let declareClass = method.getDeclaringArkClass();
            const arkFile = declareClass.getDeclaringArkFile();
            this.check1(method, targetVariableNames, options, arkFile);
        }
    };

    private check1(method: ArkMethod, targetVariableNames: string[], options: Options, arkFile: ArkFile): void {
        for (let stmt of method.getBody()?.getCfg().getStmts() ?? []) {
            // 获取语句的原始文本
            let text = stmt.getOriginalText() ?? '';
            // 如果语句的原始文本为空，直接跳过
            if (!text) {
                continue;
            }
            // 如果语句不是ArkAssignStmt类型的，则跳过
            if (!(stmt instanceof ArkAssignStmt)) {
                continue;
            }
            // 获取赋值语句的右侧操作数
            let rightOp = stmt.getRightOp();
            if (!rightOp) {
                continue;
            }
            let type = rightOp.getType();
            let typeString = type.getTypeString();
            // 通用检查类型，检查变量的类型
            this.generalInspectionType(stmt, typeString, targetVariableNames, options, text, arkFile);
            // 如果文本匹配模板字符串，并且目标变量名数组长度大于0
            if (this.matchTemplateString(text) && targetVariableNames.length > 0) {
                this.check11(text, targetVariableNames, stmt, arkFile);
            }
        }
    }

    private check11(text: string, targetVariableNames: string[], stmt: ArkAssignStmt, arkFile: ArkFile): void {
        if (this.matchTemplateString(text) && targetVariableNames.length > 0) {
            // 遍历目标变量名数组
            for (let targetVariableName of targetVariableNames) {
                // 将右侧操作数转换为字符串
                let rightOp = stmt.getRightOp().toString();
                this.handleRightOp(rightOp, targetVariableName, text, arkFile);
            }
        }
    }

    private handleRightOp(rightOp: string, targetVariableName: string, text: string, arkFile: ArkFile): void {
        // 如果右侧操作数包含目标变量名
        if (rightOp.includes(targetVariableName)) {
            // 匹配模板字符串包括变量名，例如`${target}`
            const regex = /\$\{([^}]+)\}/g;
            let matchText = text.match(regex);
            this.handleRightOp1(rightOp, targetVariableName, text, arkFile, matchText);
        }
    }
    private handleRightOp1(rightOp: string, targetVariableName: string, text: string, arkFile: ArkFile,
        matchText: RegExpMatchArray | null): void {
        if (matchText) {
            // 获取第一个匹配的模板字符串，如果有分号则去掉分号
            const target = matchText[0].split(';')?.[0];
            this.handleRightOp11(rightOp, targetVariableName, text, arkFile, target, handleRigregex);
            this.addIssueReport(arkFile, target, text);
        }
    }
    private handleRightOp11(rightOp: string, targetVariableName: string, text: string, arkFile: ArkFile,
        target: string, regex: RegExp): void {
            if (rightOp.includes(targetVariableName) && target.match(regex)) {
                this.addIssueReport(arkFile, target, text);
            }
    }
    private generalInspectionType(stmt: Stmt, typeString: string, targetVariableNames: string[],
        options: Options, text?: string, arkFile?: ArkFile): void {
        this.generalInspectionType1(stmt, typeString, targetVariableNames, options, text, arkFile);
        this.generalInspectionType2(stmt, typeString, targetVariableNames, options, text, arkFile);
    }

    private generalInspectionType1(stmt: Stmt, typeString: string, targetVariableNames: string[],
        options: Options, text?: string, arkFile?: ArkFile): void {
        this.generalInspectionType11(stmt, typeString, targetVariableNames, options, text, arkFile);
        this.generalInspectionType12(stmt, typeString, targetVariableNames, options, text, arkFile);
        this.generalInspectionType13(stmt, typeString, targetVariableNames, options, text, arkFile);
        this.generalInspectionType14(stmt, typeString, targetVariableNames, options, text, arkFile);
    }
    private generalInspectionType11(stmt: Stmt, typeString: string, targetVariableNames: string[],
        options: Options, text?: string, arkFile?: ArkFile): void {
        //所有选项都为false时，只允许string类型。
        if (options.allowAny === false && options.allowBoolean === false && options.allowNullish === false &&
            options.allowNumber === false && options.allowRegExp === false && options.allowNever === false) {
            const result = this.handleAllowNone(stmt, typeString, arkFile, text);
            if (result) {
                targetVariableNames.push(result);
            }
        }
    }
    private generalInspectionType12(stmt: Stmt, typeString: string, targetVariableNames: string[],
        options: Options, text?: string, arkFile?: ArkFile): void {
        //只有allowNumber为true时，允许number和string类型。
        if (options.allowNumber === true && options.allowAny === false && options.allowBoolean === false &&
            options.allowNullish === false && options.allowRegExp === false && options.allowNever === false) {
            const result = this.handleAllowNumber(stmt, typeString);
            if (result) {
                targetVariableNames.push(result);
            }
        }
    }
    private generalInspectionType13(stmt: Stmt, typeString: string, targetVariableNames: string[],
        options: Options, text?: string, arkFile?: ArkFile): void {
        //只有allowBoolean为true时，允许boolean和string类型。
        if (options.allowBoolean === true && options.allowAny === false && options.allowNumber === false &&
            options.allowNullish === false && options.allowRegExp === false && options.allowNever === false) {
            const result = this.handleAllowBoolean(stmt, typeString);
            if (result) {
                targetVariableNames.push(result);
            }
        }
    }
    private generalInspectionType14(stmt: Stmt, typeString: string, targetVariableNames: string[],
        options: Options, text?: string, arkFile?: ArkFile): void {
        //只有allowAny为true时，允许unknown和string类型。
        if (options.allowAny === true && options.allowBoolean === false && options.allowNumber === false &&
            options.allowNullish === false && options.allowRegExp === false && options.allowNever === false) {
            const result = this.handleAllowAny(stmt, typeString);
            if (result) {
                targetVariableNames.push(result);
            }
        }
    }

    private generalInspectionType2(stmt: Stmt, typeString: string, targetVariableNames: string[],
        options: Options, text?: string, arkFile?: ArkFile): void {
        this.generalInspectionType21(stmt, typeString, targetVariableNames, options, text, arkFile);
        this.generalInspectionType22(stmt, typeString, targetVariableNames, options, text, arkFile);
        this.generalInspectionType23(stmt, typeString, targetVariableNames, options, text, arkFile);
    }
    private generalInspectionType21(stmt: Stmt, typeString: string, targetVariableNames: string[],
        options: Options, text?: string, arkFile?: ArkFile): void {
        //只有allowNullish为true时，允许null和string类型。
        if (options.allowNullish === true && options.allowBoolean === false && options.allowNumber === false &&
            options.allowAny === false && options.allowRegExp === false && options.allowNever === false) {
            const result = this.handleAllowNullish(stmt, typeString);
            if (result) {
                targetVariableNames.push(result);
            }
        }
    }
    private generalInspectionType22(stmt: Stmt, typeString: string, targetVariableNames: string[],
        options: Options, text?: string, arkFile?: ArkFile): void {
        //只有allowRegExp为true时，允许RegExp和string类型。
        if (options.allowRegExp === true && options.allowBoolean === false && options.allowNumber === false &&
            options.allowAny === false && options.allowNullish === false && options.allowNever === false) {
            const result = this.handleAllowRegExp(stmt, typeString);
            if (result) {
                targetVariableNames.push(result);
            }
        }
    }
    private generalInspectionType23(stmt: Stmt, typeString: string, targetVariableNames: string[],
        options: Options, text?: string, arkFile?: ArkFile): void {
        //只有allowNever为true时，允许三元表达式和string类型。
        if (options.allowNever === true && options.allowBoolean === false && options.allowNumber === false &&
            options.allowAny === false && options.allowNullish === false && options.allowRegExp === false) {
            const result = this.handleAllowNever(stmt, typeString, text);
            if (result) {
                targetVariableNames.push(result);
            }
        }
    }
    private handleAllowNone(stmt: Stmt, typeString: string, arkFile?: ArkFile, text?: string): string | undefined {
        let result: string | undefined;
        // 如果操作数类型不是字符串，则将左侧操作数转换为字符串并添加到目标变量名数组中
        if (typeString !== 'string') {
            if (stmt instanceof ArkAssignStmt) {
                let leftOp = stmt.getLeftOp().toString();
                const regex = /%/g;
                const match = leftOp.match(regex);
                if (!match) {
                    result = leftOp;
                }
            }
        }
        //添加包含三元表达式的特殊情况
        const textCheck = text ?? '';
        if (typeString === 'string' && this.TernaryExpression(textCheck)) {
            if (stmt instanceof ArkAssignStmt) {
                // 匹配模板字符串包括变量名，例如`${target}`
                const regex = /\$\{([^}]+)\}/g;
                let matchText = textCheck.match(regex);
                this.handleAllowNone1(matchText, textCheck, arkFile);
            }
        }
        return result;
    }

    private handleAllowNone1(matchText: RegExpMatchArray | null, textCheck: string, arkFile?: ArkFile): void {
        if (matchText) {
            // 获取第一个匹配的模板字符串，如果有分号则去掉分号
            let target = matchText[0].split(';')?.[0];
            if (arkFile) {
                this.addIssueReport(arkFile, target, textCheck);
            }
        }
    }

    private handleAllowNumber(stmt: Stmt, typeString: string): string | undefined {
        if (typeString !== 'string' && typeString !== 'number') {
            if (stmt instanceof ArkAssignStmt) {
                let leftOp = stmt.getLeftOp().toString();
                const regex = /%/g;
                const match = leftOp.match(regex);
                if (!match) {
                    return leftOp;
                }
            }
        }
        return undefined;
    }

    private handleAllowBoolean(stmt: Stmt, typeString: string): string | undefined {
        if (typeString !== 'string' && typeString !== 'boolean') {
            if (stmt instanceof ArkAssignStmt) {
                let leftOp = stmt.getLeftOp().toString();
                const regex = /%/g;
                const match = leftOp.match(regex);
                if (!match) {
                    return leftOp;
                }
            }
        }
        return undefined;
    }

    private handleAllowAny(stmt: Stmt, typeString: string): string | undefined {
        if (typeString !== 'string' && typeString !== 'unknown') {
            if (stmt instanceof ArkAssignStmt) {
                let leftOp = stmt.getLeftOp().toString();
                const regex = /%/g;
                const match = leftOp.match(regex);
                if (!match) {
                    return leftOp;
                }
            }
        }
        return undefined;
    }

    private handleAllowNullish(stmt: Stmt, typeString: string): string | undefined {
        if (typeString !== 'string' && typeString !== 'null') {
            if (stmt instanceof ArkAssignStmt) {
                let leftOp = stmt.getLeftOp().toString();
                const regex = /%/g;
                const match = leftOp.match(regex);
                if (!match) {
                    return leftOp;
                }
            }
        }
        return undefined;
    }

    private handleAllowRegExp(stmt: Stmt, typeString: string): string | undefined {
        if (typeString !== 'string' && !this.matchRegExp(typeString)) {
            if (stmt instanceof ArkAssignStmt) {
                let leftOp = stmt.getLeftOp().toString();
                const regex = /%/g;
                const match = leftOp.match(regex);
                if (!match) {
                    return leftOp;
                }
            }
        }
        return undefined;
    }

    private handleAllowNever(stmt: Stmt, typeString: string, text?: string): string | undefined {
        const textCheck = text ?? '';
        if (typeString !== 'string' && !this.TernaryExpression(textCheck)) {
            if (stmt instanceof ArkAssignStmt) {
                let leftOp = stmt.getLeftOp().toString();
                const regex = /%/g;
                const match = leftOp.match(regex);
                if (!match) {
                    return leftOp;
                }
            }
        }
        return undefined;
    }

    // 判断文本中是否包含三元表达式
    private TernaryExpression(text: string): boolean {
        return /arg\s*===\s*'string'\s*\?\s*arg\s*:/g.test(text);
    }
    // 判断文本中是否包含模板字符串，例如`${}`
    private matchTemplateString(text: string): boolean {
        return /\$\{(.*?)\}/g.test(text);
    }
    private matchRegExp(text: string): boolean {
        return /\bRegExp\b/.test(text);
    }
    private addIssueReport(arkFile: ArkFile, name: string, text: string): void {
        const severity = this.rule.alert ?? this.metaData.severity;
        const warnInfo = this.getLineAndColumn(arkFile, name, text);
        this.metaData.description = this.metaData.description.padEnd(130);
        const filePath = arkFile.getFilePath();
        let defects = new Defects(warnInfo.line, warnInfo.startCol, warnInfo.endCol, this.metaData.description, severity, this.rule.ruleId, filePath,
            this.metaData.ruleDocPath, true, false, false);
        this.issues.push(new IssueReport(defects, undefined));
        RuleListUtil.push(defects);
    }
    private getLineAndColumn(arkfile: ArkFile, name: string, text: string): {
        line: number; startCol: number; endCol: number; filePath: string;
    } {
        if (!arkfile) {
            return { line: -1, startCol: -1, endCol: -1, filePath: '' };
        }
        const code = arkfile.getCode() ?? '';
        return this.processLines(code.split('\r\n'), name, text, arkfile.getFilePath());
    }

    private processLines(lines: string[], name: string, text: string, filePath: string): {
        filePath: string; line: number; startCol: number; endCol: number;
    } {
        for (let lineNumber = 0; lineNumber < lines.length; lineNumber++) {
            const result = this.processLine(lines[lineNumber], lineNumber + 1, name, text);
            if (result) {
                return { ...result, filePath };
            }
        }
        return { line: -1, startCol: -1, endCol: -1, filePath };
    }

    private processLine(line: string, lineNumber: number, name: string, text: string): {
        line: number; startCol: number; endCol: number;
    } | null {
        const textLineIndex = line.indexOf(text);
        if (textLineIndex === -1) { return null }

        const nameLineIndex = line.indexOf(name);
        if (nameLineIndex === -1) { return null }

        // 列号从1开始，模板字符串格式为`${}`，偏移3个字符位置
        const startCol = nameLineIndex + 3;
        const endCol = nameLineIndex + 3;
        return { line: lineNumber, startCol, endCol };
    }
}